package base.thread_demo.demo13;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

/**
 * TODO 这里errorMethod() 会有个问题,还没有解决.
 * 同步模式之保护性暂停扩展: 多个线程生产(Guarded对象)，产生对应多少个线程消费，类似于一个快递员只能负责投递一个客户的邮件。
 *
 * 异步模式之生产者/消费者：生产者和消费者不是1对1关系，数量关系上不存在等价关系，避免了邮递员资源的浪费。
 *
 * 同步之保护性暂停模式与异步之生产者/消费者模式两者区别在于：后者不需要生产/消费组线程数量上对等关系，避免了某一方线程资源的浪费。
 *
 *
 * Thread-1 开始投件：3
 * Thread-2 开始投件：1
 * Thread-0 开始投件：2   ---3个线程投件
 *
 * Thread-3开始收件：3
 * Thread-5开始收件：1
 * Thread-4开始收件：2    ---3个线程收件，且3号线程收3号件，以此类推
 *
 * Thread-5收到包裹：1, 包裹内容：快件内容2
 * Thread-3收到包裹：3, 包裹内容：快件内容1
 * Thread-4收到包裹：2, 包裹内容：快件内容0   ---3号线程的确收到3号件，以此类推
 */
public class GuardedSuspension3 {
    private static final Logger log = LoggerFactory.getLogger(GuardedSuspension3.class);

    public static void main(String[] args) throws InterruptedException {
        GuardedSuspension3 gs = new GuardedSuspension3();
        // 这种写法线程多少都不会产生异常问题
//        gs.successMethod();

        // 这种写法线程超过一定数据就会产生异常问题
        gs.errorMethod();
    }

    public void successMethod () throws InterruptedException {
        // 启动3个邮递员线程送信
        for(int i=0; i<3; i++) {
            new Thread(new Postman("快件内容"+i)).start();
        }

        // 让主线程睡眠2s,以防快递员还没有打包完成，拿不到快递单号
        Thread.sleep(2000);

        // 启动3个客户线程收信
        log.info("{}",(Futures.getIds()).size());

        for (int i = 0; i < 3; i++) {
            new Thread(new People(i)).start();
        }
    }

    public void errorMethod () throws InterruptedException {
        // 这里为了演示问题,故意启动10个邮递员线程送信
        for(int i=0; i<10; i++) {
            new Thread(new Postman("快件内容"+i)).start();
        }

        // 让主线程睡眠2s,以防快递员还没有打包完成，拿不到快递单号
        Thread.sleep(2000);

        // 启动10个客户线程收信
        log.info("{}",(Futures.getIds()).size());
        Set<Integer> ids = Futures.getIds();
        for(Integer id: ids) {
            new Thread(new People(id)).start();
        }
    }
}


// 客户类：负责收信业务
class People implements Runnable {
    private static final Logger log = LoggerFactory.getLogger(People.class);
    private Integer id;

    public People(Integer id) {
        this.id = id;
    }

    @Override
    public void run() {
        Guarded guarded = Futures.pop(id);
        log.info("{} 开始收 {} 号件", Thread.currentThread().getName(), id);
        Object obj = guarded.get(1000);
        log.info("{} 收到 {} 号件包裹, 包裹内容：{}",Thread.currentThread().getName(), guarded.getId(), obj);
    }
}

// 邮递员类：负责送信业务
class Postman implements Runnable {
    private static final Logger log = LoggerFactory.getLogger(Postman.class);
    private String mail;
    public Postman(String mail) {
        this.mail = mail;
    }

    @Override
    public void run() {
        Guarded guarded = Futures.push();
        log.info("{} 开始投 {} 号件", Thread.currentThread().getName(), guarded.getId());
        guarded.set(mail);
    }
}


/**
 * 封装Guarded的集合类，提供一个线程安全的Hashtable。理解为快递柜
 * 用于解耦生产者线程和消费者线程之间的数据共享问题。
 */
class Futures {
    // 封装Guarded的集合
    private static Map<Integer, Guarded> boxes = new Hashtable<>();//对于成员属性来说，static表示整个内存只有一份数据

    private static int id=1;

    // 往boxes中塞数据
    // boxes是线程安全的对象，无需push()方法synchronized
    public static Guarded push() {// 对于方法来说，static表示可以直接类名.method()进行调用
        Guarded guarded = new Guarded(generatedId());
        boxes.put(guarded.getId(), guarded);
        return guarded;
    }

    // 从boxes中取数据
    //boxes是线程安全的，无需pop()方法synchronized，
    //不会出现两个线程同时拿到boxes，一个remove(1),后一个remove(2),导致后一个结果覆盖了前一个线程执行结果，1号元素没有删掉问题。
    public static Guarded pop(int id) {
        return boxes.remove(id);//remove()会先返回结果，并从HashTable集合中移除数据
    }

    // 类似快递包裹打包产生取件码
    public static synchronized int generatedId() {//锁是Class字节码对象
        return id++;
    }

    public static Set<Integer> getIds(){
        return boxes.keySet();
    }
}


/**
 * 将共享数据 response 封装成 Guarded 对象
 *
 * synchronized 底层关联 Monitor的监视器对象。监视通信数据response的传递。该类理解为一个快递包裹。
 * 用于同步监视response数据一定时间(timeout)。
 *
 */
class Guarded {
    // 添加区分Guarded对象的唯一标识，理解为快递单号
    private int id;
    public Guarded(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }

    // 递交的通信数据,理解为快递实物
    private Object response;

    public synchronized Object get(long timeout) {
        // 执行获取数据开始时间
        long begin = System.currentTimeMillis();
        // 已等待时间
        long passed = 0;
        while(response == null) {
            // 剩余等待时间
            long wait = timeout - passed;
            if(wait <= 0) {
                break;
            }
            try {
                this.wait(wait);// 因存在虚假唤醒可能，wait时间不能直接是timeout
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            passed = System.currentTimeMillis() - begin;
        }

        return response;
    }

    public synchronized void set(Object response) {
        this.response = response;
        this.notifyAll();
    }
}
